/*
 *          Water.glsl
 *
 *      This is a 'GLSL 4.10 core profile' code.
 *      *Works in Progress*
 */

//------------------------------------------------------------------------------


-- TessControl


/*
  Supposed to be the same that Terrain.glsl so check if it can work
  without any problems.
*/


--

//------------------------------------------------------------------------------


-- TessEval

// PARAMs
layout(quads) in;
layout(fractional_odd_spacing) in;
layout(cw) in;

// INs
in vec2 tcPosition[];

// OUTs
out block
{
  vec3 position;
  vec3 normal;
  vec2 texCoord;
  //vec4 depthmap;
  //vec3 positionWS;
} Output;


// UNIFORMs
uniform sampler2D uDepthTex;
uniform float uPatchSize;
uniform float uTerrainScaledResolution;
uniform float uWaterHeightBumpScale;
uniform float uWaterMicroBumpUVScale;//
uniform float uWaterBumpUVShift;//

uniform vec3 uEyePosWS;
uniform mat4 uModelViewProjMatrix;


vec4 computerWaterNormal(vec3);

void main()
{  
  // Location on the quad patch
  vec2 uv = gl_TessCoord.xy;
  
  // Position
  vec3 position;
  position.xz = tcPosition[0] + uPatchSize * uv;
  position.y  = -0.5f * uWaterHeightBumpScale;
  
  // Normal
  vec4 normal = computeWaterNormal( position );
  
  // TexCoord
  vec2 texCoord = position.xz / uTerrainScaledResolution;
  
#if 0
  // Depthmap
  vec4 depthmap = texture( uDepthTex, texCoord);
  
  // Fading out displacement & normal near shores by 60% XXX
  const vec3 up( 0.0f, 1.0f, 0.0f);
  const float factor = 0.4f + 0.6f * depthmap.g;
  
  normal = mix( up, normalize(normal.xyz),  factor);
  
  position.y  += normal.w * uWaterHeightBumpScale * factor;
  position.xz -= normal.xz * 0.5f * factor;
#endif
  
  // Output params
  Output.position = uModelViewProjMatrix * vec4( position, 1.0f);
  Output.normal   = normalize( normal.xyz );
  Output.texCoord = texCoord * uWaterMicroBumpUVScale + uWaterBumpUVShift*0.07f;//
  //Output.depthmap = depthmap; //
  //Output.positionWS = position;
}

vec4 computeWaterNormal(in vec3 positionWS)
{
  vec4 normal( 0.0f, 4.0f, 0.0f, 0.0f);
  
  float distToEye = distance( uEyePosWS, positionWS);
  vec2 uv = positionWS.xz * uWaterMicroBumpUVScale / uTerrainScaledResolution;
  float lod = 0.0f;//
  
  
  float uvScale = 1.0f;
  vec2 variance( 1.0f );
  vec2 disturbance( 1.0f );
  
  for (int i=0; i<5; ++i)
  {
    vec2 bump_uv = uv * uvScale + 0.03f*uWaterBumpUVShift*variance;
    vec4 waterBump = textureLod( uWaterBumpTex, bump_uv, lod);
    
    normal.xz += disturbance.x * (2.0f*waterBump.xz - 1.0f);
    normal.w  += disturbance.y * (waterBump.w - 0.5f);
    
    uvScale *= 1.4f;
    variance.x = -variance.x;
    disturbance *= 0.65f;
  }
  
  normal.xyz = normalize( normal.xyz );
  normal.w *= uWaterHeightBumpScale;
  
  return normal;
}


--

//------------------------------------------------------------------------------


-- Fragment


in block
{
  vec3 position;
  vec3 normal;
  vec2 texCoord;
  //vec4 depthmap;
  //vec3 positionWS;
} Input;


out vec4 fragColor;


// Texture sampler
uniform sampler2D uDepthTex; //
uniform sampler2D uReflectionTex;
uniform sampler2D uRefractionTex;

// View dependent
uniform mat4 uModelViewMatrix;
uniform mat4 uModelViewProjMatrix;
uniform mat4 uLightMVP;
uniform vec3 uEyePosWS;

uniform vec3 uLightPosWS; // XXX

// Visual parameters
uniform vec3  uWaterDeepColor         = vec3( 0.0f, 0.3f, 0.75f);
uniform vec3  uWaterScatterColor      = vec3( 0.3f, 0.7f, 0.6f);
uniform vec3  uWaterSpecularColor     = vec3( 1.0f, 1.0f, 1.0f);
uniform float uWaterSpecularIntensity = 350.0f; //XXX
uniform float uWaterSpecularPower     = 1000.0f;
uniform float uFogDensity             = 1.0f / 700.0f;

uniform vec2 uTexelSize;


void main()
{

/// SHADOW ------------------------------

  // calculating pixel position in light space
  vec4 positionLS = uLightMVP * vec4( Input.positionWS, 1.0f);
  positionLS.xyz /= positionLS.w;
  positionLS.x = (positionLS.x + 1.0f) * 0.5f; // XXX
  positionLS.y = (1.0f - positionLS.y) * 0.5f; // XXX

  // calculating shadow multiplier to be applied to diffuse/scatter/specular light components
  const vec2  shadowOffset = vec2( 1.0f, -1.0f) / 4096.0f;
  const vec2  shadowUV   = position.LS.xy;
  const float shadowBias = 0.995f * positionLS.z;

  float shadowFactor = 0.0f;
  shadowFactor  = texture( uDepthTex, shadowUV, shadowBias).r;
  shadowFactor += texture( uDepthTex, shadowUV + shadowOffset.xx, shadowBias).r;
  shadowFactor += texture( uDepthTex, shadowUV + shadowOffset.xy, shadowBias).r;
  shadowFactor += texture( uDepthTex, shadowUV + shadowOffset.yx, shadowBias).r;
  shadowFactor += texture( uDepthTex, shadowUV + shadowOffset.yy, shadowBias).r;
  shadowFactor *= 0.2f; // == 1.0f / 5



/// MICROBUMP NORMAL --------------------
    
  vec3 n1 = texture( uWaterBumpTex, Input.texCoord - 0.2f*uWaterBumpUVShift).rgb;
  vec3 n2 = texture( uWaterBumpTex, 0.5f*Input.texCoord - 0.05f*uWaterBumpUVShift).rgb;

  const vec3 n_offset( 1.0f, -8.0f, 1.0f);
  vec3 microbump_normal = normalize(n1 - n_offset) + normalize(n2 - n_offset);
  
  
  // XXX not sure
  mat3x3 normalRotation;
  normalRotation[1] = Input.normal.xyz; // normal
  normalRotation[2] = cross( vec3(0.0f, 0.0f,-1.0f), normalRotation[1]);
  normalRotation[2] = normalize( normalRotation[2] ); // binormal
  normalRotation[0] = cross( normalRotation[2], normalRotation[1]);
  normalRotation[0] = normalize( normalRotation[0] ); // tangent
  
  microbump_normal = normalize(normalRotation * microbump_normal);
  

/// SCATTERING----------------------
  
  vec3 fragToEye   = normalize( uEyePosWS - Input.positionWS );
  vec3 fragToLight = normalize( uLightPosWS - Input.positionWS ); 

  // only the crests of water waves generate double refracted light //~~
  float scatterFactor = 2.5f * max( 0.0f, 0.25f * (1.0f + Input.positionWS.y));

  // the waves that lie between camera and light projection on water plane generate maximal amount of double refracted light  //~~
  float scatterExp = max( 0.0f, dot( normalize(vec3(fragToLight.x, 0.0f, fragtoLight.z)), -fragToEye));
  scatterFactor *= pow( scatterExp, 2.0f);

  // the slopes of waves that are oriented back to light generate maximal amount of double refracted light  //~~
  scatterExp = max( 0.0f, 1.0f - dot( fragToLight, microbump_normal));
  scatterFactor *= pow( scatterExp, 8.0f);

  // XXX----
  // water crests gather more light than lobes, so more light is scattered under the crests  //~~
  scatterFactor += 1.5f * g_WaterColorIntensity.y * 
                   max( 0.0f, input.positionWS.y + 1.0f) *
                   // the scattered light is best seen if observing direction is normal to slope surface
                   max( 0.0f, dot( fragToEye, microbump_normal)) *
                   // fading scattered light out at distance and if viewing direction is vertical to avoid unnatural look
                   max(0, 1.0f-fragToEye.y)*(300.0f/(300.0f + distance(uEyePosWS, Input.positionWS)));

  scaterFactor *= shadowFactor;

  // fading scatter out by 90% near shores so it looks better
  scatterFactor *= 0.1f + 0.9f*Input.depthmap_scaler.g;
  // XXX----



  // XXX----
/// FRESNEL FACTOR (refraction) ---------------
  // calculating fresnel factor 
  float r = (1.2f-1.0f) / (1.2f+1.0f);
  float tmp = pow( 1.0f - dot( microbump_normal, fragToEye), 4); //XXX
  float fresnelFactor = clamp( 0.0f, 1.0f, r + (1.0f-r)*tmp);

  // calculating specular factor
  float eyeToFragReflected = -fragToEye + 2 * dot( fragToEye, microbump_normal) * microbump_normal;
  
  float specularExponent = max( 0.0f, dot( fragToLight, eyeToFragReflected));
  float specularFactor = shadowFactor * fresnelFactor * 
                          pow( specularExponent, uWaterSpecularPower);
  // XXX----
  
  
  
  // Diffuse
  float diffuseFactor = max( 0.0f, dot( fragToLight, microbump_normal));
  
    

/// DISTURBANCE ---------------
    // calculating disturbance which has to be applied to planar reflections/refractions to give plausible results
    vec3 disturbanceVS = mat3x3(uModelViewMatrix) * vec3( microbump_normal.x, 0.0f, microbump_normal.z);

    vec2 reflectionDisturbance = disturbanceVS.xz * 0.03f;
    vec2 refractionDisturbance = vec2(-disturbanceVS.x, disturbanceVS.y) * 0.05f *
        // fading out reflection disturbance at distance so reflection doesn't look noisy at distance
        (20.0f/(20 + distance( uEyePosWS, Input.positionWS)));
    
    
    // calculating correction that shifts reflection up/down according to water wave Y position
    vec4 projected_waveheight = uModelViewProjMatrix * vec4( Input.positionWS, 1.0f);

    float waveheight_correction = -0.5f * projected_waveheight.y / projected_waveheight.w;
    projected_waveheight = uModelViewProjMatrix * vec4( Input.positionWS.x, -0.8f, Input.positionWS.z, 1.0f);
    waveheight_correction += 0.5f * projected_waveheight.y / projected_waveheight.w;

    reflectionDisturbance.y = max( -0.15f, waveheight_correction+reflection_disturbance.y);

#if 0
    // picking refraction depth at non-displaced point, need it to scale the refraction texture displacement amount according to water depth
    
    float refractionDepth = GetRefractionDepth(input.position.xy*g_ScreenSizeInv); //XXX
    refractionDepth = g_ZFar*g_ZNear/(g_ZFar-refraction_depth*(g_ZFar-g_ZNear));
    float4 vertex_in_viewspace=mul(float4(input.positionWS,1),g_ModelViewMatrix);
    water_depth=refraction_depth-vertex_in_viewspace.z;
    float nondisplaced_water_depth=water_depth;
    
    // scaling refraction texture displacement amount according to water depth, with some limit
    refraction_disturbance *= min( 2.0f, waterDepth);

    // picking refraction depth again, now at displaced point, need it to calculate correct water depth
    refraction_depth=GetRefractionDepth(input.position.xy*g_ScreenSizeInv+refraction_disturbance);
    refraction_depth=g_ZFar*g_ZNear/(g_ZFar-refraction_depth*(g_ZFar-g_ZNear));
    vertex_in_viewspace=mul(float4(input.positionWS,1),g_ModelViewMatrix);
    water_depth=refraction_depth-vertex_in_viewspace.z;

    // zeroing displacement for points where displaced position points at geometry which is actually closer to the camera than the water surface
    float conservative_refraction_depth=GetConservativeRefractionDepth(input.position.xy*g_ScreenSizeInv+refraction_disturbance);
    conservative_refraction_depth=g_ZFar*g_ZNear/(g_ZFar-conservative_refraction_depth*(g_ZFar-g_ZNear));
    vertex_in_viewspace=mul(float4(input.positionWS,1),g_ModelViewMatrix);
    float conservative_water_depth=conservative_refraction_depth-vertex_in_viewspace.z;

    if(conservative_water_depth<0)
    {
        refraction_disturbance=0;
        water_depth=nondisplaced_water_depth;
    }
#endif

  waterDepth = max( 0.0f, waterDepth);
  
  //---  
  

/// FINAL COMPOSITION ----------------------------------

  // Water color
  vec3 waterColor = diffuseFactor * uWaterDeepColor;

  // Applied an athmospheric fog
  vec3 fogColor = computeFogColor( fragToLight, fragToEye);
  float fogFactor = min( 1.0f, exp(-distance(uEyePosWS, Input.positionWS)*uFogDensity));
  waterColor = mix( fogColor, waterColor,  fogFactor);

  
  // Get disturb reflection & refraction
  vec3 reflectionColor = texture( uReflectionTex, Input.position.xy * uTexelSize + reflectionDisturbance).rgb;
  vec3 refractionColor = texture( uRefractionTex, Input.position.xy * uTexelSize + refractionDisturbance).rgb;
  
  /*
  // fading fresnel factor to 0 to soften water surface edges
  //fresnelFactor *= min( 1.0f , 5.0*waterDepth); ~~~XXX

  // fading refraction color to water color according to distance that refracted ray travels in water 
  //refractionColor = mix( waterColor, refractionColor, min( 1.0f, exp(-waterDepth/8.0))); ~~~XXX
  */

  vec3 specularColor = specularFactor * fresnelFactor * uWaterSpecularColor;
  vec3 scatterColor  = scatterFactor * uWaterScatterColor;

  // Final water color
  fragColor.rgb = mix( refractionColor, reflectionColor,  fresnelFactor);
  fragColor.rgb += specularColor + scatterColor;
  fragColor.a = 1.0f;
}


